/***************************************************************************
 *   Copyright (c) 2021 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "pch.h"


#include <cassert>

#include "Base/Vector3D.h"


#include "Sketcher/App/GeometryFacade.h"

#include "Sketcher/App/Constraint.h"

#include <Sketcher/App/GeoList.h>

using namespace Sketcher;

// Vector is moved
template <typename T>
GeoListModel<T>::GeoListModel(std::vector<T>&& geometrylist,
                              int intgeocount,
                              bool ownerT): geomlist(std::move(geometrylist)),
                                            intGeoCount(intgeocount),
                                            OwnerT(ownerT),
                                            indexInit(false) {}

// Vector is shallow copied (copy constructed)
template <typename T>
GeoListModel<T>::GeoListModel(const std::vector<T>& geometrylist,
                              int intgeocount): geomlist(geometrylist), // copy constructed here
                                                intGeoCount(intgeocount),
                                                OwnerT(false),
                                                indexInit(false) {}

template <typename T>
GeoListModel<T>::~GeoListModel() {
    /*if(OwnerT) {
        for(auto & g : geomlist)
            delete g;
    }*/
}

template <typename T>
GeoListModel<T> GeoListModel<T>::getGeoListModel(std::vector<T>&& geometrylist, int intgeocount, bool ownerT) {
    return GeoListModel(std::move(geometrylist), intgeocount, ownerT);
}

template <typename T>
const GeoListModel<T> GeoListModel<T>::getGeoListModel(const std::vector<T>& geometrylist, int intgeocount) {
    return GeoListModel(geometrylist, intgeocount);
}


template <typename T>
int GeoListModel<T>::getGeoIdFromGeomListIndex(int index) const {
    assert(index < static_cast<int>(geomlist.size()));

    if (index < intGeoCount)
        return index;
    return (index - geomlist.size());
}

template <typename T>
const Part::Geometry* GeoListModel<T>::getGeometryFromGeoId(const std::vector<T>& geometrylist, int geoId) {
    if constexpr (std::is_same<T, GeometryPtr>()) {
        if (geoId >= 0)
            return geometrylist[geoId];
        return geometrylist[geometrylist.size() + geoId];
    } else if constexpr (std::is_same<T, GeometryFacadeUniquePtr>()) {
        if (geoId >= 0)
            return geometrylist[geoId]->getGeometry();
        return geometrylist[geometrylist.size() + geoId]->getGeometry();
    }
}

template <typename T>
const GeometryFacade* GeoListModel<T>::getGeometryFacadeFromGeoId(const std::vector<T>& geometrylist, int geoId) {
    if constexpr (std::is_same<T, GeometryPtr>()) {
        if (geoId >= 0)
            return GeometryFacade::getFacade(geometrylist[geoId]).release();
        return GeometryFacade::getFacade(geometrylist[geometrylist.size() + geoId]).release();
    } else if constexpr (std::is_same<T, GeometryFacadeUniquePtr>()) {
        if (geoId >= 0)
            return geometrylist[geoId].get();
        return geometrylist[geometrylist.size() + geoId].get();
    }
}

// this function is used to simulate cyclic periodic negative geometry indices (for external geometry)
template <typename T>
const Part::Geometry* GeoListModel<T>::getGeometryFromGeoId(int geoId) const {
    return GeoListModel<T>::getGeometryFromGeoId(geomlist, geoId);
}

template <typename T>
const GeometryFacade* GeoListModel<T>::getGeometryFacadeFromGeoId(int geoId) const {
    return GeoListModel<T>::getGeometryFacadeFromGeoId(geomlist, geoId);
}

template <typename T>
Base::Vector3d GeoListModel<T>::getPoint(int geoId, PointPos pos) const {
    const Part::Geometry* geo = getGeometryFromGeoId(geoId);

    return getPoint(geo, pos);
}

template <typename T>
Base::Vector3d GeoListModel<T>::getPoint(const GeoElementId& geid) const {
    return getPoint(geid.GeoId, geid.Pos);
}

template <typename T>
Base::Vector3d GeoListModel<T>::getPoint(const Part::Geometry* geo, PointPos pos) const {
    using namespace Sketcher;

    if (geo->getTypeId() == Part::GeomPoint::getClassTypeId()) {
        auto p = static_cast<const Part::GeomPoint*>(geo);
        if (pos == PointPos::start || pos == PointPos::mid || pos == PointPos::end)
            return p->getPoint();
    } else if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
        auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo);
        if (pos == PointPos::start)
            return lineSeg->getStartPoint();
        if (pos == PointPos::end)
            return lineSeg->getEndPoint();
    } else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) {
        auto circle = static_cast<const Part::GeomCircle*>(geo);
        if (pos == PointPos::mid)
            return circle->getCenter();
    } else if (geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) {
        auto ellipse = static_cast<const Part::GeomEllipse*>(geo);
        if (pos == PointPos::mid)
            return ellipse->getCenter();
    } else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
        auto aoc = static_cast<const Part::GeomArcOfCircle*>(geo);
        if (pos == PointPos::start)
            return aoc->getStartPoint(/*emulateCCW=*/true);
        if (pos == PointPos::end)
            return aoc->getEndPoint(/*emulateCCW=*/true);
        if (pos == PointPos::mid)
            return aoc->getCenter();
    } else if (geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) {
        auto aoc = static_cast<const Part::GeomArcOfEllipse*>(geo);
        if (pos == PointPos::start)
            return aoc->getStartPoint(/*emulateCCW=*/true);
        if (pos == PointPos::end)
            return aoc->getEndPoint(/*emulateCCW=*/true);
        if (pos == PointPos::mid)
            return aoc->getCenter();
    } else if (geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) {
        auto aoh = static_cast<const Part::GeomArcOfHyperbola*>(geo);
        if (pos == PointPos::start)
            return aoh->getStartPoint();
        if (pos == PointPos::end)
            return aoh->getEndPoint();
        if (pos == PointPos::mid)
            return aoh->getCenter();
    } else if (geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) {
        auto aop = static_cast<const Part::GeomArcOfParabola*>(geo);
        if (pos == PointPos::start)
            return aop->getStartPoint();
        if (pos == PointPos::end)
            return aop->getEndPoint();
        if (pos == PointPos::mid)
            return aop->getCenter();
    } else if (geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) {
        auto bsp = static_cast<const Part::GeomBSplineCurve*>(geo);
        if (pos == PointPos::start)
            return bsp->getStartPoint();
        if (pos == PointPos::end)
            return bsp->getEndPoint();
    }

    return Base::Vector3d();
}

template <typename T>
void GeoListModel<T>::rebuildVertexIndex(void) const {
    VertexId2GeoElementId.clear();
    GeoElementId2VertexId.clear();

    int geoId = 0;
    int pointId = 0;

    auto addGeoElement = [this, &pointId](int geoId, PointPos pos) {
        VertexId2GeoElementId.emplace_back(geoId, pos);
        GeoElementId2VertexId.emplace(std::piecewise_construct,
                                      std::forward_as_tuple(geoId, pos),
                                      std::forward_as_tuple(pointId++));
    };

    if (geomlist.size() <= 2)
        return;
    for (auto it = geomlist.begin(); it != geomlist.end(); ++it, geoId++) {
        Base::Type type;

        if constexpr (std::is_same_v<T, Part::Geometry*>)
            type = (*it)->getTypeId();
        else if constexpr (std::is_same_v<T, std::unique_ptr<const GeometryFacade>>)
            type = (*it)->getGeometry()->getTypeId();

        if (geoId > getInternalCount())
            geoId = -getExternalCount();

        if (type == Part::GeomPoint::getClassTypeId()) {
            addGeoElement(geoId, PointPos::start);
        } else if (type == Part::GeomLineSegment::getClassTypeId() ||
            type == Part::GeomBSplineCurve::getClassTypeId()) {
            addGeoElement(geoId, PointPos::start);
            addGeoElement(geoId, PointPos::end);
        } else if (type == Part::GeomCircle::getClassTypeId() ||
            type == Part::GeomEllipse::getClassTypeId()) {
            addGeoElement(geoId, PointPos::mid);
        } else if (type == Part::GeomArcOfCircle::getClassTypeId() ||
            type == Part::GeomArcOfEllipse::getClassTypeId() ||
            type == Part::GeomArcOfHyperbola::getClassTypeId() ||
            type == Part::GeomArcOfParabola::getClassTypeId()) {
            addGeoElement(geoId, PointPos::start);
            addGeoElement(geoId, PointPos::end);
            addGeoElement(geoId, PointPos::mid);
        }
    }

    indexInit = true;
}

template <typename T>
GeoElementId GeoListModel<T>::getGeoElementIdFromVertexId(int vertexId) {
    if (!indexInit) // lazy initialised
        rebuildVertexIndex();

    return VertexId2GeoElementId[vertexId];
}

template <typename T>
int GeoListModel<T>::getVertexIdFromGeoElementId(const GeoElementId& geoelementId) const {
    if (!indexInit) // lazy initialised
        rebuildVertexIndex();

    auto found = std::find(VertexId2GeoElementId.begin(), VertexId2GeoElementId.end(), geoelementId);

    if (found != VertexId2GeoElementId.end())
        return std::distance(found, VertexId2GeoElementId.begin());

    throw std::string("GeoElementId not indexed");
}


namespace Sketcher {
    // Template specialisations
    template <>
    GeoListModel<GeometryFacadeUniquePtr>::GeoListModel(
        std::vector<GeometryFacadeUniquePtr>&& geometrylist,
        int intgeocount,
        bool ownerT) : geomlist(std::move(geometrylist)),
                       intGeoCount(intgeocount),
                       OwnerT(false),
                       indexInit(false) {
        // GeometryFacades hold the responsibility for releasing the resources.
        //
        // This means that each GeometryFacade that is passed to the GeoListModel,
        // must set the ownership (GF->setOwner(true)), if it should be the owner.
        // Otherwise, it follows the default behaviour that some other class, which
        // created the pointer, is responsible for freeing it.
        //
        // Under the Single Responsibility Principle GeoListModel cannot be made
        // responsible for releasing those pointers.
        assert(ownerT == false);
    }

    template <>
    GeoListModel<GeometryFacadeUniquePtr>::GeoListModel(
        const std::vector<GeometryFacadeUniquePtr>& geometrylist,
        int intgeocount): intGeoCount(intgeocount),
                          OwnerT(false),
                          indexInit(false) {
        // GeometryFacades are movable, but not copiable, so they need to be reconstructed (shallow copy of vector)
        // Under the Single Responsibility Principle, these will not take over a responsibility that shall be enforced
        // on the original GeometryFacade. Use the move version of getGeoListModel if moving the responsibility is intended.

        geomlist.reserve(geometrylist.size());

        for (auto& v : geometrylist) {
            geomlist.push_back(GeometryFacade::getFacade(v->getGeometry()));
        }
    }


    // instantiate the types so that other translation units can access template constructors
    template class GeoListModel<Part::Geometry*>;
#if !defined(__MINGW32__)
    template class GeoListModel<std::unique_ptr<const GeometryFacade>>;
#else
// Remark: It looks like when implementing a method of GeoListModel for GeometryFacadeUniquePtr then under MinGW
// the explicit template instantiation doesn't do anything. As workaround all other methods must be declared separately
template const Part::Geometry* GeoListModel<GeometryFacadeUniquePtr>::getGeometryFromGeoId(int geoId) const;
template const Sketcher::GeometryFacade* GeoListModel<GeometryFacadeUniquePtr>::getGeometryFacadeFromGeoId(int geoId) const;
template int GeoListModel<GeometryFacadeUniquePtr>::getGeoIdFromGeomListIndex(int index) const;
template int GeoListModel<GeometryFacadeUniquePtr>::getVertexIdFromGeoElementId(const Sketcher::GeoElementId &) const;
template GeoElementId GeoListModel<GeometryFacadeUniquePtr>::getGeoElementIdFromVertexId(int);
template Base::Vector3d GeoListModel<GeometryFacadeUniquePtr>::getPoint(int geoId, Sketcher::PointPos pos) const;
template Base::Vector3d GeoListModel<GeometryFacadeUniquePtr>::getPoint(const GeoElementId &) const;
#endif
} // namespace Sketcher

GeoListFacade Sketcher::getGeoListFacade(const GeoList& geolist) {
    std::vector<std::unique_ptr<const GeometryFacade>> facade;
    facade.reserve(geolist.geomlist.size());

    for (auto geo : geolist.geomlist)
        facade.push_back(GeometryFacade::getFacade(geo));

    auto geolistfacade = GeoListFacade::getGeoListModel(std::move(facade), geolist.getInternalCount());

    return geolistfacade;
}
